﻿using System;
using System.IO;
using System.Globalization;
using System.Collections.Generic;
using Aeon.Framework.Objects.Models.ModelBVH;

namespace Aeon.Framework.Objects.Models
{

/// <summary>
/// Parser and loader for .bvh file animations.
/// Caches last loaded model only.
/// 
/// </summary>
public sealed class ParserBVH : IParserModel
    {
    // --- Inner:

    /// <summary>
    /// BVH file parser.
    /// 
    /// </summary>
    private class Reader
        {
        // --- Fields:

        private NumberFormatInfo culture;   // BVH culture for number formatting.
        private List<Channel> channels;     // List of file channels.
        
        private Motion motion;              // File Motion.
        private Joint root;                 // Root Joint.

        private int counter = 0;            // End site unique name counter.

        // --- Properties:

        public Motion Motion { get { return(motion); } }
        public Joint Root { get { return(root); } }

        // --- Constructor:

        public Reader()
            {
            culture = new NumberFormatInfo();
            culture.NumberDecimalSeparator = ".";

            channels = new List<Channel>();

            motion = null;
            root = null;
            }

        // --- External Methods:

        /// <summary>
        /// Parses a BVH file and returns true if joints and motion are extracted successfully.
        /// Can throw exceptions.
        /// 
        /// </summary>
        public bool ParseFile(String filepath)
            {
            if (!File.Exists(filepath)) { throw new FileNotFoundException("Couldn't find "+filepath+"."); }
            TextReader stream = new StreamReader(filepath);

            if (!stream.ReadLine().Contains("HIERARCHY")) { throw new FormatException("Couldn't parse file as BVH."); }

            parseJoints(stream,root);

            string line;
            double time = 0;
            ushort frames = 0;
    		
            line = stream.ReadLine();
            if (!line.Contains("Frames")) { throw new FormatException("Couldn't parse BVH Frame section."); }
            else {
                 line = line.Replace("Frames:"," ");
                 line = line.Trim();

                 frames = Convert.ToUInt16(line);
                 }

            line = stream.ReadLine();
            if (!line.Contains("Time")) { throw new FormatException("Couldn't parse BVH Timer section."); }
            else {
                 line = line.Replace("Frame Time:"," ");
                 line = line.Trim();

                 time = Convert.ToDouble(line.Replace('.',','));
                 }

            motion = parseFrames(stream,time,frames,channels.Count);
            
            stream.Close();
            return(true);
            }

        // --- Internal Methods:

        /// <summary>
        /// Parses each frame channel data and returns a motion animation object.
        /// Can throw exceptions.
        /// 
        /// </summary>
        private Motion parseFrames(TextReader str, double spf, ushort frm, int chn)
            {
            Motion animation = new Motion(spf, frm, (ushort)chn);
            Double[,] data = new Double[frm,chn];
            String line;

            //for(ushort frame = 0; (str.Peek() != -1); frame++)
            for(ushort frame = 0; frame<frm; frame++)
                {
                line = str.ReadLine();
                line = line.Replace("\t"," ");
                line = line.Replace("  "," ");
                line = line.Trim();

                string[] vector = line.Split(' ');

                for (ushort channel = 0; channel < chn; channel++) data[frame, channel] = Convert.ToDouble(vector[channel],culture);
                }

            animation.createData(data);
            return(animation);
            }

        /// <summary>
        /// Parses individual Joints recursively.
        /// Can throw exceptions.
        /// 
        /// </summary>
        private Boolean parseJoints(TextReader str, Joint parent)
            {
            String line;
            Joint joint;

            while(true)
                {
                line = str.ReadLine();

                if (line.Contains("ROOT") || line.Contains("JOINT") || line.Contains("End"))
                    {
                    bool end = (line.Contains("End")) ? true:false;
                    string[] name = line.Trim().Split(' ');
                    if (end) counter++;

                    // Header:
                    String jointName = (end) ? ("End "+name[1]+counter):(name[1]);
                    joint = new Joint(jointName,parent);

                    // Root?
                    if (parent == null) root = joint;
                    else parent.addChild(joint);

                    // Open Brace:
                    line = str.ReadLine(); 
     
                    // Offsets:
                    line = str.ReadLine();
                    line = line.Replace("OFFSET"," ");
                    line = line.Replace("\t"," ");
                    line = line.Replace("  "," ");
                    line = line.Trim();

                    string[] off = line.Split(' ');                        
                    float x = Convert.ToSingle(off[0],culture);
                    float y = Convert.ToSingle(off[1],culture);
                    float z = Convert.ToSingle(off[2],culture);
                    
                    joint.Position = new Position(x,y,z);

                    // Channels:
                    if (!end)
                       {
                       line = str.ReadLine();
                       line = line.Replace("CHANNELS"," ");
                       line = line.Replace('\t',' ');
                       line = line.Replace("  "," ");
                       line = line.Trim();

                       string[] chan = line.Split(' ');
                       ushort nchan = Convert.ToUInt16(chan[0]);

                       for(ushort cnt = 1; cnt<=nchan; cnt++)
                           {
                           Channel ch = new Channel(chan[cnt]);

                           joint.addChannel(ch);
                           channels.Add(ch);
                           }

                       if (!parseJoints(str,joint)) return(false);
                       }

                    // Closing Brace:
                    char peek = (char)str.Peek();
                    while (peek == ' ' || peek == '\t') { str.Read(); peek = (char)str.Peek(); }

                    peek = (char)str.Peek();                    
                    if (peek != '}') { continue; }
                    else {
                         str.ReadLine();
                         peek = (char)str.Peek();
                         while (peek == ' ' || peek == '\t') { str.Read(); peek = (char)str.Peek(); }
                         
                         if (peek == '}') return(true);
                         }
                    }

                else {
                     //line = str.ReadLine();

                     if (line.Contains("MOTION")) return(false);
                     }
                }
            }
        
        }

    // --- Attributes:

    private String previous;                // Currently cached model path.
    private AdapterBVH cache;               // Currently cached model.

    // --- Constructor:

    public ParserBVH()
        {
        previous = String.Empty;
        }
    
    // --- Public Methods:
    
    public IModelAdapter Parse(String path)        
        {
        if (previous == path) return(cache);
        else {
             Reader read = new Reader();
             DataBVH data = new DataBVH();

             try {
                 if (read.ParseFile(path))
                    {
                    data.Root = read.Root;
                    data.Motion = read.Motion;
                    }
                 }

             catch(Exception err) { throw err; }

             cache = new AdapterBVH(data);
             previous = path;
             return(cache);
             }
        }

    }


}